package com.foundationmade.newbiz.core.security.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;

import org.hibernate.tool.hbm2x.StringUtils;
import org.jasypt.digest.StandardStringDigester;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.foundationmade.newbiz.core.security.domain.SecurityIdentity;
import com.foundationmade.newbiz.core.security.service.SecurityService;

import devcken.datatables.serverside.FromServer;
import devcken.datatables.serverside.SortQuery;
import devcken.datatables.serverside.ToServer;
import devcken.hibernate.query.OrderBy;
import devcken.utility.Pair;

/**
 * 보안 계정 관련 요청을 처리하기 위한 인터페이스를 제공한다.
 * @author Devcken
 */
@Controller
@RequestMapping("/security")
public class SecurityController
{
	private final static Logger logger = LoggerFactory.getLogger(SecurityController.class);
	
	/**
	 * 보안 관련 DAO를 주입받는다.
	 */
	@Resource(name="securityService")
	private SecurityService _securityService;
	
	/**
	 * 계정 암호를 암호화하기 위한 다이제스터를 주입받는다.
	 */
	@Resource(name="jasyptStringDigest")
	private StandardStringDigester _passwordDigester;
	
	/**
	 * 보안 계정 인증 페이지로 이동한다.
	 * @return 보안 계정 인증 페이지에 대한 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.POST })
	public ModelAndView login()
	{
		return new ModelAndView("tiles.login");
	}
	
	/**
	 * 보안 계정의 인증에 실패한 경우 로그인 페이지로 이동한다.
	 * @param model 
	 * @return 보안 계정 인증 페이지에 대한 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value = "/login/failed", method = { RequestMethod.GET, RequestMethod.POST })
	public ModelAndView loginFailed(ModelMap model)
	{
		model.addAttribute("loginFailed", true);
		
		return new ModelAndView("tiles.login", model);
	}
	
	/**
	 * 보안 계정 메인 페이지로 이동한다.
	 * @return 보안 계정 메인 페이지에 대한 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity", method = { RequestMethod.GET, RequestMethod.POST })
	public ModelAndView securityIdentityMain()
	{
		return new ModelAndView("security.identity.list");
	}
	
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/write", method = { RequestMethod.GET, RequestMethod.POST })
	public ModelAndView securityIdentityWrite()
	{
		return new ModelAndView("security.identity.write");
	}
	
	/**
	 * 보안 계정 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param page 보안 계정 목록에 대해 조회하고자 하는 페이지 번호가 전달된다.
	 * @param searchCondition 검색 조건이 전달된다.
	 * @return 조회된 보안 계정 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/list", method = { RequestMethod.POST, RequestMethod.GET })
	public ModelAndView listSecurityIdentities(HttpServletRequest request,
			ToServer toServer,
			SecurityIdentity searchCondition)
	{
		FromServer map = new FromServer();
		
		try
		{
			List<Pair<String, OrderBy>> sortConditions = SortQuery.getSortConditions(request, toServer.getiSortingCols());
			
			List<SecurityIdentity> identities = new ArrayList<SecurityIdentity>();
			
			Integer filteredCount = this._securityService.listSecurityIdentities(searchCondition,
					toServer.getPage(),
					toServer.getiDisplayLength(),
					sortConditions,
					identities);
			
			map.setiTotalRecords(this._securityService.countSecurityIdentities());
			map.setiTotalDisplayRecords(filteredCount);
			
			map.put("identities", identities);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 보안 계정 전체 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @return 조회된 보안 계정 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/list.all", method = { RequestMethod.POST, RequestMethod.GET })
	public ModelAndView listSecurityIdentities(HttpServletRequest request)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<SecurityIdentity> identities = this._securityService.listSecurityIdentities();
			
			map.put("identities", identities);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 보안 계정 열람 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param identityId 열람하려는 보안 계정의 아이디가 전달된다.
	 * @return 열람된 보안 계정을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/get", method = RequestMethod.POST)
	public ModelAndView getSecurityIdentity(HttpServletRequest request,
			@RequestParam(value = "identityId", required = true)Long identityId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			SecurityIdentity identity = this._securityService.getSecurityIdentity(identityId);
			
			// 보안 상 보안 비밀번호를 제거한다.
			identity.setSecurityPassword(null);
			
			map.put("identity", identity);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 인증되어 있는 보안 계정 정보 열람 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @return 열람된 보안 계정을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/get/authenticated", method = RequestMethod.POST)
	public ModelAndView getAuthenticatedSecurityIdentity(HttpServletRequest request)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			
			User user = (User)authentication.getPrincipal();
			
			SecurityIdentity identity = this._securityService.getSecurityIdentity(user.getUsername());
			
			map.put("identity", identity);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 보안 아이디 중복 검사 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param securityId 중복 검사하려는 보안 아이디가 전달된다.
	 * @return 보안 아이디 중복 검사 결과를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/find", method = { RequestMethod.POST })
	public ModelAndView findSecurityIdentityBy(HttpServletRequest request,
			@RequestParam(value = "securityId", required = true)String securityId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			boolean duplicated = this._securityService.findSecurityIdentityBy(securityId);
			
			map.put("duplicated", duplicated);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 보안 계정 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param identity 저장하려는 보안 계정 정보가 전달된다. 
	 * @return 보안 계정 저장 결과를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/save", method = RequestMethod.POST)
	public ModelAndView saveSecurityIdentity(HttpServletRequest request,
			SecurityIdentity identity)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			if (StringUtils.isEmpty(identity.getName().trim()))
			{
				map.put("result", false);
				map.put("errorCode", 2);
				map.put("errorMessage", "이름이 입력되지 않았습니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			identity.setEnabled(true);
			identity.setLocked(false);
			identity.setSecurityPassword(this._passwordDigester.digest(identity.getSecurityId()));
			
			// 별도의 세부 권한 구분이 없으므로 권한 키를 ROLE_ADMIN으로 한정한다.
			identity.setAuthorityKey("ROLE_ADMIN");
			
			boolean result = this._securityService.saveSecurityIdentity(identity);
			
			map.put("result", result);
			map.put("identity", identity);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
			
			map.put("result", false);
			map.put("errorCode", 1);
			map.put("errorMessage", error.getMessage());
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 보안 계정 수정 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param renewIdentity 수정하려는 보안 계정 정보가 전달된다.
	 * @param renewPassword 새로운 비밀번호가 전달된다.
	 * @param renewPasswordCheck 새로운 비밀번호 확인이 전달된다.
	 * @return 보안 계정 저장 결과를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RolesAllowed("ROLE_ADMIN")
	@RequestMapping(value = "/identity/update", method = RequestMethod.POST)
	public ModelAndView updateSecurityIdentity(HttpServletRequest request,
			SecurityIdentity renewIdentity,
			@RequestParam(value = "identityId", required = true)Long identityId,
			@RequestParam(value = "renewPassword", required = true)String renewPassword,
			@RequestParam(value = "renewPasswordCheck", required = true)String renewPasswordCheck)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			if (StringUtils.isEmpty(renewIdentity.getSecurityPassword().trim()))
			{
				map.put("result", false);
				map.put("errorCode", 1);
				map.put("errorMessage", "기존 암호가 입력되지 않았습니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			if (StringUtils.isEmpty(renewPassword))
			{
				map.put("result", false);
				map.put("errorCode", 2);
				map.put("errorMessage", "새로운 암호가 입력되지 않았습니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			if (StringUtils.isEmpty(renewPasswordCheck))
			{
				map.put("result", false);
				map.put("errorCode", 3);
				map.put("errorMessage", "새로운 암호 확인이 입력되지 않았습니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			if (!renewPassword.equals(renewPasswordCheck))
			{
				map.put("result", false);
				map.put("errorCode", 4);
				map.put("errorMessage", "새로운 암호와 확인이 동일하지 않습니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			if (StringUtils.isEmpty(renewIdentity.getName().trim()))
			{
				map.put("result", false);
				map.put("errorCode", 5);
				map.put("errorMessage", "이름이 입력되지 않았습니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			SecurityIdentity identity = this._securityService.getSecurityIdentity(renewIdentity.getSecurityId());
			
			if (!this._passwordDigester.matches(renewIdentity.getSecurityPassword(), identity.getSecurityPassword()))
			{
				map.put("result", false);
				map.put("errorCode", 6);
				map.put("errorMessage", "기존 암호가 틀립니다.");
				
				return new ModelAndView("jsonView", map);
			}
			
			String renewPasswordDigest = this._passwordDigester.digest(renewPassword);
			
			renewIdentity.setSecurityPassword(renewPasswordDigest);
			
			renewIdentity.setIdentityId(identityId);
			
			this._securityService.saveSecurityIdentity(renewIdentity);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
			
			map.put("result", false);
			map.put("errorCode", 1);
			map.put("errorMessage", error.getMessage());
		}
		
		return new ModelAndView("jsonView", map);
	}
}